package com.opennews.openplatform.myspringbootcore.security

import com.opennews.openplatform.myspringbootcore.util.IdField
import io.jsonwebtoken.ExpiredJwtException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys
import org.springframework.stereotype.Component
import java.time.Instant
import java.util.*

@Component
class TokenManager {
    /**
     * Generates signed token.
     *
     * @param claims The input claims to generate token.
     * @param signingKey The key to sign token.
     * @param expiration How long the token expires in seconds.
     * @return String of generated and signed token. AKA jws.
     */
    fun generateToken(claims: MyClaims, signingKey: String, expiration: Int): String {
        // Gets current instant for token issuedAt field.
        val epochSecond = Instant.now().epochSecond
        claims.issuedAt = epochSecond

        // Sets the expiresAt if needed.
        if (expiration > 0) {
            claims.expiresAt = epochSecond + expiration
        }

        // Prepares sign key for jws token generation.
        val key = Keys.hmacShaKeyFor(signingKey.toByteArray())

        return Jwts.builder()
            .expiration(claims.expiresAt?.let { Date(it * 1000) })
            .issuedAt(Date(claims.issuedAt!! * 1000))
            .issuer(claims.issuer)
            .notBefore(claims.notBefore?.let { Date(it * 1000) })
            .subject(claims.subject)
            .claim("accountGroupId", claims.accountGroupId)
            .claim("id", claims.id)
            .claim("username", claims.username)
            .claim("deviceId", claims.deviceId)
            .claim("roles", claims.roles)
            .signWith(key)
            .compact()
    }

    /**
     * Validates jws and returns a ParsedToken instance.
     *
     * @param jws:        Generated and signed token.
     * @param signingKey: The key to unsign token.
     */
    fun validateToken(jws: String, signingKey: String): ParsedToken {
        val parsedToken = ParsedToken()
        val key = Keys.hmacShaKeyFor(signingKey.toByteArray())

        try {
            val jwsClaims = Jwts.parser()
                .verifyWith(key)
                .build()
                .parseSignedClaims(jws)
                .payload

            parsedToken.valid = true

            parsedToken.claims = MyClaims().apply {
                accountGroupId = IdField.Text(jwsClaims["accountGroupId"].toString())
                id = IdField.Text(jwsClaims["id"].toString())
                username = jwsClaims["username"].toString()
                deviceId = jwsClaims["deviceId"] as String?
                issuedAt = jwsClaims["iat"] as Long
                expiresAt = jwsClaims["exp"] as Long?
                roles = jwsClaims["roles"] as List<String>
            }
        } catch (ex: Exception) {
            parsedToken.valid = false

            if (ex is ExpiredJwtException) {
                parsedToken.expired = true
            }
        }

        return parsedToken
    }
}